package com.imooc.coupon.executor;

import com.imooc.coupon.constant.CouponCategory;
import com.imooc.coupon.constant.RuleFlag;
import com.imooc.coupon.exception.CouponException;
import com.imooc.coupon.support.vo.SettlementInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 优惠券结算规则执行管理器，根据用户传入的结算信息 {@link SettlementInfo}
 * 找到具体的结算规则执行器；Note: Bean 后置处理器 {@link BeanPostProcessor} ：
 * 当 Spring 为所有的 Bean 创建完对象之后调用
 *
 * Created By TINY
 * Create Time 2020/2/12
 **/
@Slf4j
@Component
public class ExecutorManager implements BeanPostProcessor {

    /* 对应 RuleFlag 的所有的规则执行器 */
    private static final Map<RuleFlag, RuleExecutor> executorIndex =
            new HashMap<>(RuleFlag.values().length);

    /**
     * 优惠券规则计算入口，调用方需要保证传入的优惠券个数 >= 1
     *
     * @param settlementInfo {@link SettlementInfo} 结算信息
     * @return {@link SettlementInfo} 经过计算处理后的结算信息
     * @throws CouponException 业务异常
     */
    public SettlementInfo computeRule(
            SettlementInfo settlementInfo) throws CouponException {

        SettlementInfo result = null;

        // 单张优惠券结算
        if (settlementInfo.getCouponAndTemplateInfos().size() == 1) {
            // 获取优惠券的类别
            CouponCategory couponCategory = CouponCategory.of(
                    settlementInfo.getCouponAndTemplateInfos()
                            .get(0).getTemplate().getCategory()
            );

            switch (couponCategory) {
                case FULL_REDUCTION:
                    result = executorIndex.get(RuleFlag.FULL_REDUCTION)
                            .computeRule(settlementInfo);
                    break;
                case REDUCTION:
                    result = executorIndex.get(RuleFlag.REDUCTION)
                            .computeRule(settlementInfo);
                    break;
                case DISCOUNT:
                    result = executorIndex.get(RuleFlag.DISCOUNT)
                            .computeRule(settlementInfo);
                    break;
            }
        } else {  // 组合类优惠券结算

            // 组合优惠券的类别
            List<CouponCategory> categories = new ArrayList<>(
                    settlementInfo.getCouponAndTemplateInfos().size()
            );

            settlementInfo.getCouponAndTemplateInfos().forEach(ct ->
                    categories.add(CouponCategory.of(ct.getTemplate().getCategory())));

            if (categories.size() != 2) {
                throw new CouponException("不支持超过两张优惠券的结算！");
            } else {
                // 包含满减 & 折扣优惠券
                if (categories.contains(CouponCategory.FULL_REDUCTION)
                        && categories.contains(CouponCategory.DISCOUNT)) {
                    result = executorIndex.get(RuleFlag.FULL_REDUCE_DISC)
                            .computeRule(settlementInfo);
                } else {
                    throw new CouponException("暂不支持其它类别的组合优惠券结算！");
                }
            }
        }

        return result;
    }

    /**
     * 在 Bean 初始化（属性赋值）之前执行该方法
     *
     * @param bean Bean 对象
     * @param beanName 对应的 Bean 保存在 Spring 容器中的名称
     * @return Bean 对象
     * @throws BeansException Bean 异常
     */
    @Override
    public Object postProcessBeforeInitialization(
            @NonNull Object bean, String beanName) throws BeansException {

        // 当前 Bean 不是 RuleExecutor 的实例，直接返回
        if (!(bean instanceof RuleExecutor)) {
            return bean;
        }

        RuleExecutor ruleExecutor = (RuleExecutor) bean;
        RuleFlag ruleFlag = ruleExecutor.supports(); // 得到规则执行器的支持类型

        // 如果当前规则执行器 Map 中已经注册了对应的规则执行器，抛出异常（重复注册）
        if (executorIndex.containsKey(ruleFlag)) {
            throw new IllegalStateException(ruleFlag.getDescription()
                    + ") 规则执行器已经存在！");
        }

        log.info("注册规则执行器（{}）, 对应规则枚举类型（{}）",
                ruleExecutor.getClass(), ruleFlag);

        executorIndex.put(ruleFlag, ruleExecutor);

        return bean;
    }

    /**
     * 在 Bean 初始化（属性赋值）之后执行该方法
     *
     * @param bean Bean 对象
     * @param beanName 对应 Bean 保存到 Spring 容器的名称
     * @return Bean 对象
     * @throws BeansException Bean 异常
     */
    @Override
    public Object postProcessAfterInitialization(
            @NonNull Object bean, String beanName) throws BeansException {

        return bean;
    }
}
